﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace PlayBox.DataClasses
{
    /// <summary>
    /// simulates the functionality of a TPM
    /// all methods in this class are considered to be inside of a TPM
    /// this class is implemented as singleton
    /// 
    /// the TPM is also used to decrypted the keys that are encrypted
    /// with the SRK (on the disk)
    /// </summary>
    class TPM
    {
        private static TPM instance = null;
        private RijndaelManaged AES;
        private String IV = "u49!r?}uj4v7'0j~";
        private byte[] KeyBytes;
        private RNGCryptoServiceProvider rand;
        private TPM() {
            initCrypto();
        }
        /// <summary>
        /// gets the instance of the TPM that should be used
        /// </summary>
        /// <returns></returns>
        private static TPM getInstance()
        {
            if (instance == null)
            {
                instance = new TPM();
            }
            return instance;
        }
        /// <summary>
        /// intializes the crypto parameters
        /// note: ECB is used because only single blocks are encrypted -> used for encrypting keys
        /// </summary>
        private void initCrypto()
        {
            KeyBytes = getKeyFromDisk("TPM\\StorageRootKey.txt");

            AES = new RijndaelManaged();
            //used for encrypting keys, ECB is OK here
            AES.Mode = CipherMode.ECB;
            AES.BlockSize = 128;
            //we operate on full blocks only, padding would double the required space (add another padded block)
            AES.Padding = PaddingMode.None;

            rand = new RNGCryptoServiceProvider();
        }
        /// <summary>
        /// this method represents a cryptographically secure random number generator
        /// it will return a new random key on every call
        /// </summary>
        /// <returns>a completely random key</returns>
        private byte[] getRandomKey()
        {
            byte[] randSeq = new byte[128/8];
            rand.GetBytes(randSeq);
            return randSeq;
        }
        /// <summary>
        /// retrieves an encrypted key from the given path
        /// </summary>
        /// <param name="filepath">the path to the key</param>
        /// <returns>an encrypted key</returns>
        private static byte[] getKeyFromDisk(string filepath)
        {
            byte[] key = new byte[128 / 8];
            FileStream stream = new FileStream(filepath, FileMode.Open);
            stream.Read(key, 0, 128 / 8);
            stream.Close();
            return key;
        }
        /// <summary>
        /// returns the key path of a given user
        /// </summary>
        /// <param name="user">the user</param>
        /// <returns>the key path</returns>
        private static string getKeyPath(string user)
        {
            return "HDD\\Users\\" + user + "\\Profile\\Key.txt";
        }
        /// <summary>
        /// decrypts the key of a given user and returns it
        /// </summary>
        /// <param name="User">the user</param>
        /// <returns>the key of the user</returns>
        public static byte[] getUserKey(string User)
        {
            byte[] key = getKeyFromDisk(getKeyPath(User));
            ICryptoTransform decryptor = getInstance().AES.CreateDecryptor(getInstance().KeyBytes, Encoding.ASCII.GetBytes(getInstance().IV));
            
            byte[] input = key;
            byte[] output = new byte[128/8];
            output = decryptor.TransformFinalBlock(input, 0, 128 / 8);
            return output;
        }
        /// <summary>
        /// generates a new random key for a given user and stores it on the disk
        /// </summary>
        /// <param name="User">the user to generate the key for</param>
        public static void generateUserKey(string User) {
            byte[] key = getInstance().getRandomKey();
            ICryptoTransform encryptor = getInstance().AES.CreateEncryptor(getInstance().KeyBytes, Encoding.ASCII.GetBytes(getInstance().IV));

            string path = getKeyPath(User);

            if (!Directory.Exists(Path.GetDirectoryName(path)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(path));
            }

            FileStream fstream = new FileStream(path, FileMode.Create);
            CryptoStream cstream = new CryptoStream(fstream, encryptor, CryptoStreamMode.Write);
            cstream.Write(key, 0, 128 / 8);
            cstream.Close();
        }
        /// <summary>
        /// converts any given byte[] to a string
        /// </summary>
        /// <param name="arr">the byte[] to convert</param>
        /// <returns>the string representation of the given byte[]</returns>
        private static string ByteArrayToString(byte[] arr)
        {
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            return enc.GetString(arr);
        }
    }
}
